﻿package org.kolonitsky.alexey.display {
	
	import caurina.transitions.Tweener;
	
	import com.gskinner.sprites.CollisionDetection;
	
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.display.Graphics;
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.MouseEvent;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.clearInterval;
	import flash.utils.setInterval;
	
	import org.kolonitsky.alexey.debug.DebugWrapper;
	import org.kolonitsky.alexey.events.ViewManagerEvent;
	
	/**
	 * Make display object dragable.
	 */
	public class ViewManager extends EventDispatcher 
	{
		public static const MIN_ZOOM:Number = 0.15;
		public static const MAX_ZOOM:Number = 1.75;
		
		public static const MARGIN_TOP:Number = 10;
		public static const MARGIN_RIGHT:Number = 10;
		public static const MARGIN_BOTTOM:Number = 10;
		public static const MARGIN_LEFT:Number = 10;
		
		public static const MIN_NORMAL_MOOV_ZOOM:Number = 0.5;
		
		public static const MOVE_TIME_STEP:Number = 2.5;
		public static const ZOOM_STEP:Number = 0.1;
		public static const ZOOM_TIME_STEP:Number = 0.5;
		
		public const MIN_AUTHORIZED_MAP_WIDTH:Number = 50;
		public const MIN_AUTHORIZED_MAP_HEIGHT:Number = 50;
		
		private var targetObjectContainer:Sprite;
		private var targetObjectContainerParent:DisplayObjectContainer;
		private var stageSizesObject:Sprite;
		
		private var screenShot:Bitmap = null;
		private var bitmapContainer:Sprite = null;
		
		private var isZoomRun:Boolean = false;
		private var isMoveRun:Boolean = false;
		public var rect:Rectangle = null;
		
		//-----------------------------------
		// paddings
		//-----------------------------------
		
		public var marginTop:Number = MARGIN_TOP;
		public var marginRight:Number = MARGIN_RIGHT;
		public var marginBottom:Number = MARGIN_BOTTOM;
		public var marginLeft:Number = MARGIN_LEFT;
		
		
		//-----------------------------------
		// target stage
		//-----------------------------------
		
		public function get stage():Stage
		{
			if (_targetObject)
				return _targetObject.stage;
			return null;
		}
		
		
		//-----------------------------------
		// target object
		//-----------------------------------
		
		private var _targetObject:DisplayObject = null;
		
		public function get targetObject():DisplayObject {
			return _targetObject;
		}
		
		
		//-----------------------------------
		// zoom point
		//-----------------------------------
		
		/**
		 *  Show point on map in the window center 
		 */
		public function get zoomPoint():Point  
		{
			// Stage center in target object coordinate system
			var stageCenter:Point = new Point(stage.stageWidth / 2, stage.stageHeight / 2);
			var c:Point = targetObject.globalToLocal(stageCenter);
			DebugWrapper.log("DEBUG: get scale: " + currentScale + ", c" + c + ", stageCenter" + stageCenter);
			
			return c; 
		}
		
		public function set zoomPoint(value:Point):void
		{
			// Distance from center to zoomPoint value scaled with current scale
			var d:Point = value.subtract(zoomPoint);
			
			bitmapContainer.x = targetObjectContainer.x -= d.x * currentScale;
			bitmapContainer.y = targetObjectContainer.y -= d.y * currentScale;  
			DebugWrapper.log("DEBUG: corrected zoom point " + zoomPoint);
		}
		
		
		//-----------------------------------
		// current scale
		//-----------------------------------
		
		private var _currentScale:Number = 0.0;
		
		public function get currentScale():Number 
		{
			return _currentScale;
		}
		
		public function set currentScale(value:Number):void 
		{
			_currentScale = value;
			targetObjectContainer.scaleX = _currentScale;
			targetObjectContainer.scaleY = _currentScale;
		}
		
		
		//---------------------------------------
		// Minimal zoom value
		//---------------------------------------
		
		private var _minScale:Number = MIN_ZOOM;
		
		public function get minScale():Number
		{
			return _minScale;
		}
		
		public function set minScale(value:Number):void
		{
			_minScale = value;
		}
		
		
		//---------------------------------------
		// Minimal zoom value
		//---------------------------------------
		
		private var _maxScale:Number = MAX_ZOOM;
		
		public function get maxScale():Number
		{
			return _maxScale;
		}
		
		public function set maxScale(value:Number):void
		{
			_maxScale = value;
		}
		
		
		/**
		 *  Dragable target
		 *  @target is dragable and scalabel object
		 */
		public function ViewManager(target:Sprite) 
		{
			_targetObject = target;
			if (targetObject.parent)
			{
				init();
			}
			else
			{
				_targetObject.addEventListener(Event.ADDED_TO_STAGE, init);
			}
		}
			
		
		/**
		 *  Redraw target object image inside ViewManager.
		 */
		public function updateBitmap():void
		{
			var currentTargetOblectScale:Number = targetObjectContainer.scaleX;
			
			targetObjectContainer.x = 0;
			targetObjectContainer.y = 0;
			currentScale = 1.0;
			//targetObjectContainer.alpha = 0.5;
			
			rect = targetObjectContainer.getBounds(targetObjectContainerParent);
			
			var bitmapData:BitmapData = new BitmapData(rect.width, rect.height, true, 0xFF0000);
			bitmapData.draw(targetObjectContainer, new Matrix(1, 0, 0, 1, -rect.x, -rect.y), null, null, null, true);
			screenShot = new Bitmap(bitmapData, "auto", true);
			screenShot.visible = false;
			screenShot.x = rect.x;
			screenShot.y = rect.y;
			//screenShot.alpha = 0.5;
			
			while(bitmapContainer.numChildren)
				bitmapContainer.removeChildAt(0);
			bitmapContainer.addChild(screenShot);
			
			//drawCenter(this.targetObjectContainer, 0x009900, 100);
			
			currentScale = currentTargetOblectScale;
		}
		
		/**
		 *  Reset target scale and positon to fit window
		 */
		public function initPosition():void {
			currentScale = 1.0;
			targetObjectContainer.x = 0;
			targetObjectContainer.y = 0;
			targetObject.x = 0;
			targetObject.y = 0;
			
			bitmapContainer.scaleX = 1;
			bitmapContainer.scaleY = 1;
			bitmapContainer.x = 0;
			bitmapContainer.y = 0;
			screenShot.x = rect.x;
			screenShot.y = rect.y;
			
			var zoomStepWidth:Number = targetObjectContainer.stage.stageWidth / targetObjectContainer.width;
			var zoomStepHeight:Number = targetObjectContainer.stage.stageHeight / targetObjectContainer.height;
			var newZoom:Number = Math.min(zoomStepWidth, zoomStepHeight);
			var zoomStep:Number = newZoom - _currentScale;
			
			var finalZoom:Number = _currentScale + zoomStep;
			var centerX:Number = (stage.stageWidth - targetObjectContainer.width * finalZoom) / 2
			var centerY:Number = (stage.stageHeight - targetObjectContainer.height * finalZoom) / 2
			
			zoomBy(zoomStep, 0);
			moveTo(0, centerX, centerY, targetObjectContainer.x, targetObjectContainer.y);
		}
		
		/**
		 *  Change image zoom by delta.
		 *  
		 *  @param delta
		 *  @duration animation delay
		 */
		public function zoomBy(delta:Number, duration:Number):void
		{
			var newScale:Number = _currentScale + delta;
			zoom(newScale, duration);
		}
		
		/**
		 *  Zoom target object
		 */
		public function zoom(newScale:Number, timeStep:Number=0.0):void 
		{
//			trace("ViewManager.zoom()");
			if (newScale > maxScale)
				newScale = maxScale;
			if (newScale < minScale)
				newScale = minScale;
			
			if (newScale == currentScale)
				return;
			
			if (!isZoomRun) {
				bitmapContainer.x = targetObjectContainer.x;
				bitmapContainer.y = targetObjectContainer.y;
				bitmapContainer.scaleX = _currentScale;
				bitmapContainer.scaleY = _currentScale;
			}
			
			isZoomRun = true;
			
			if (timeStep == 0)
			{
				currentScale = newScale;
				tweenCompleteCallback();
				return;
			}
			
			initMapChange();
			currentScale = newScale;
			
			Tweener.addTween(bitmapContainer, {
				scaleX: newScale, 
				scaleY: newScale, 
				time: timeStep, 
				onComplete: tweenCompleteCallback
			});
		}
		
		/**
		 *  Zoom relating x, y coordinates
		 */
		public function zoomTo(scaleStep:Number, timeStep:Number, x:Number, y:Number):void 
		{
			if (isMoveRun)
				return;
			
			var localPoint:Point = targetObjectContainerParent.globalToLocal(new Point(x, y));
			
			var shiftZeroX:Number = localPoint.x - targetObjectContainer.x;
			var shiftZeroY:Number = localPoint.y - targetObjectContainer.y;
			
			targetObject.x -= shiftZeroX / _currentScale;
			targetObject.y -= shiftZeroY / _currentScale;
			
			targetObjectContainer.x += shiftZeroX;
			targetObjectContainer.y += shiftZeroY;
			
			screenShot.x = targetObject.x + rect.x;
			screenShot.y = targetObject.y + rect.y;
			
			bitmapContainer.x = targetObjectContainer.x;
			bitmapContainer.y = targetObjectContainer.y;
			
			zoomBy(scaleStep, timeStep);
		}
		
		/*--------------------------------------------------------------------------------------------
		/*---------------------------------------Move-------------------------------------------------
		/*------------------------------------------------------------------------------------------*/
		
		public function move(moveStepX:Number, moveStepY:Number, timeStep:Number):void 
		{
			moveTo(timeStep, moveStepX, moveStepY, 0, 0);	
		}
		
		public function moveTo(timeStep:Number, newMouseX:Number, newMouseY:Number, oldMouseX:Number, oldMouseY:Number):void 
		{
//			trace("ViewManager.moveTo()");
			
			isMoveRun = true;
			var indentX:Number = newMouseX - oldMouseX;
			var indentY:Number = newMouseY - oldMouseY;
			
			initMapChange();
			
			bitmapContainer.scaleX = currentScale;
			bitmapContainer.scaleY = currentScale;
			
			targetObjectContainer.x += indentX;
			targetObjectContainer.y += indentY;
			
			var isValid:Boolean = isTrackingTargetOnStage(targetObjectContainer);
			if (!isValid) 
			{
				targetObjectContainer.x -= indentX;
				targetObjectContainer.y -= indentY;
				isMoveRun = false
				return;
			}
			
			if (timeStep == 0)
			{
				bitmapContainer.x = targetObjectContainer.x;
				bitmapContainer.y = targetObjectContainer.y;
				tweenCompleteCallback();
				return;
			}
			
			Tweener.addTween(bitmapContainer, {
				x: targetObjectContainer.x, 
				y: targetObjectContainer.y, 
				time: timeStep, 
				onComplete: tweenCompleteCallback
			});
		}
		
		private function init(event:Event = null):void
		{
			if (targetObject.parent == null)
				return;
			
			if (_targetObject.hasEventListener(Event.ADDED_TO_STAGE))
				_targetObject.removeEventListener(Event.ADDED_TO_STAGE, init);
			
			stageSizesObject = new Sprite();
			
			//
			this.targetObjectContainerParent = targetObject.parent;
			
			targetObjectContainer = new Sprite();
			targetObjectContainer.addChild(targetObject);
			
			targetObjectContainerParent.addChild(targetObjectContainer);
			
			//DEBUG
			drawSizeObject();
			
			this.bitmapContainer = new Sprite();
			bitmapContainer.buttonMode = true;
			
			updateBitmap();
			
			targetObjectContainerParent.addChild(bitmapContainer);
			targetObjectContainerParent.addChildAt(stageSizesObject, 0);
			
			targetObjectContainer.addEventListener(MouseEvent.MOUSE_DOWN, targetObject_mouseDownHandler);
			bitmapContainer.addEventListener(MouseEvent.MOUSE_DOWN, targetObject_mouseDownHandler);
			
			stage.addEventListener(MouseEvent.MOUSE_WHEEL, stage_mouseWhellHandler);
		}
		
		private function isTrackingTargetOnStage(target:Sprite):Boolean 
		{
			drawSizeObject();
			var crossingRectangle:Rectangle = CollisionDetection.checkForCollision(target, stageSizesObject, 0);
			
			if (!crossingRectangle)
				return false;
			if (crossingRectangle.width < target.width / 10)
				return false;
			if (crossingRectangle.height < target.height / 10)
				return false;
			return true;
		}
		
		private function initMapChange():void {
			targetObjectContainer.visible = false;
			screenShot.visible = true;
		}
		
		private function tweenCompleteCallback():void 
		{
//			trace("ViewManager.tweenCompleteCallback()");
			
			targetObjectContainer.visible = true;
			screenShot.visible = false;
			
			if (isZoomRun) 
			{
				var type:String = ViewManagerEvent.END_ZOOM_EVENT;
				dispatchEvent(new ViewManagerEvent(type, true, false, targetObjectContainer.x, targetObjectContainer.y, currentScale));
				isZoomRun = false;
			}
			isMoveRun = false;
		}
		
		
		//---------------------------------------------------------------------
		// Helper methods
		//---------------------------------------------------------------------
		
		/**
		 * Utility function for debug purpus;
		 */
		private function drawSizeObject():void {
			stageSizesObject.graphics.clear();
			stageSizesObject.graphics.beginFill(0x00FF00, 1.0);
			stageSizesObject.visible = false;
			stageSizesObject.graphics.drawRect(0, 0, targetObjectContainer.stage.stageWidth, targetObjectContainer.stage.stageHeight);
			stageSizesObject.graphics.endFill();
		}
		
		/**
		 * Utility function for debug purpus;
		 */
		public function drawCenter(displayObject:Sprite, color:uint, radius:Number):void {
			var gr:Graphics = displayObject.graphics
			gr.beginFill(color);
			gr.drawCircle(0, 0, radius);
			gr.endFill();
		}
		
		
		//---------------------------------------------------------------------
		// Drag and scale event listeners
		//---------------------------------------------------------------------
		
		private var initMouseX:Number;
		private var initMouseY:Number;
		
		private function targetObject_mouseDownHandler(event:MouseEvent):void {
			initMouseX = event.stageX;
			initMouseY = event.stageY;
			stage.addEventListener(MouseEvent.MOUSE_MOVE, stage_mouseDownHandler);
			stage.addEventListener(MouseEvent.MOUSE_UP, stage_mouseUpHandler);
		}
		
		private function stage_mouseDownHandler(event:MouseEvent):void {
			moveTo(MOVE_TIME_STEP, event.stageX, event.stageY, initMouseX, initMouseY);
			initMouseX = event.stageX;
			initMouseY = event.stageY;
		}
		
		private function stage_mouseUpHandler(event:MouseEvent):void
		{
			stage.removeEventListener(MouseEvent.MOUSE_MOVE, stage_mouseDownHandler);
			stage.removeEventListener(MouseEvent.MOUSE_UP, stage_mouseUpHandler);
		}
		
		private function stage_mouseWhellHandler(event:MouseEvent):void
		{
			zoomTo(event.delta * ZOOM_STEP, ZOOM_TIME_STEP, event.stageX, event.stageY);
		}

	}
}

